﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2022-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI.FrameDecoder
*
* SunnyUI.FrameDecoder.dll can be used for free under the MIT license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: TagLengthValueFrameDecoder.cs
* 文件说明: 标签、数据长度、数据 - 数据帧解码器
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2020-01-01: V1.0.0 增加文件说明
******************************************************************************/

using System;

namespace Sunny.FrameDecoder
{
    /// <summary>
    /// 标签、数据长度、数据 - 数据帧解码器
    /// </summary>
    public class TagLengthValueFrameDecoder : BaseByteFrameDecoder
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="tagLength">标签长度</param>
        /// <param name="valueLengthType">数据长度类型</param>
        /// <param name="valueLengthIsLittleEndian">数据长度字节顺序</param>
        /// <param name="isFullLength">数据长度是否包含标签、数据长度的长度，false为仅数据长度</param>
        /// <param name="maxFrameLength">最大数据长度，仅判断数据长度</param>
        public TagLengthValueFrameDecoder(int tagLength, ValueLengthType valueLengthType, bool valueLengthIsLittleEndian = true, bool isFullLength = false, int maxFrameLength = 0)
        {
            if (tagLength < 0)
            {
                throw new ArgumentOutOfRangeException("Tag length must be greater than or equal to zero.");
            }

            TagLength = tagLength;
            ValueLengthType = valueLengthType;
            IsFullLength = isFullLength;
            MaxFrameLength = maxFrameLength;
            ValueLengthIsLittleEndian = valueLengthIsLittleEndian;
            FrameExceptDataLength = TagLength + ValueLengthType.Length();
        }

        private readonly bool ValueLengthIsLittleEndian;

        /// <summary>
        /// 标签长度
        /// </summary>
        public int TagLength { get; }

        /// <summary>
        /// 数据长度类型
        /// </summary>
        public ValueLengthType ValueLengthType { get; }

        /// <summary>
        /// 数据长度是否包含标签、数据长度的长度，false为仅数据长度
        /// </summary>
        public bool IsFullLength { get; }

        /// <summary>
        /// 解码函数
        /// </summary>
        protected override void Decoding()
        {
            if (Cache.WrittenCount < FrameExceptDataLength) return;

            byte[] tag = Cache.WrittenSpan.Slice(0, TagLength).ToArray();
            int len = Cache.WrittenSpan.GetLengthValue(TagLength, ValueLengthType.Length(), ValueLengthIsLittleEndian);
            int dataLen = IsFullLength ? len - FrameExceptDataLength : len;

            if (dataLen < 0)
            {
                Reset();
                return;
            }

            while (dataLen >= 0)
            {
                if (MinFrameLength > 0 && dataLen < MinFrameLength)
                {
                    DecoderError("The data length is less than the minimum length", Cache.WrittenSpan.ToArray());
                    Reset();
                    break;
                }

                if (MaxFrameLength > 0 && MaxFrameLength > MinFrameLength && dataLen > MaxFrameLength)
                {
                    DecoderError("The data length is greater than the maximum length.", Cache.WrittenSpan.ToArray());
                    Reset();
                    break;
                }

                int allLen = dataLen + TagLength + ValueLengthType.Length();
                if (allLen <= Cache.WrittenCount)
                {
                    LastDecodedTime = DateTime.Now;
                    var e = new TagLengthValueFrameDataEventArgs(
                        this,
                        tag,
                        len,
                        Cache.WrittenSpan.Slice(TagLength + ValueLengthType.Length(), dataLen).ToArray(),
                        Cache.WrittenSpan.Slice(0, allLen).ToArray());
                    DoDecoder(this, e);
                }
                else
                {
                    break;
                }

                Cache.Remove(allLen);
                if (Cache.WrittenCount < FrameExceptDataLength) return;

                tag = Cache.WrittenSpan.Slice(0, TagLength).ToArray();
                len = Cache.WrittenSpan.GetLengthValue(TagLength, ValueLengthType.Length(), ValueLengthIsLittleEndian);
                dataLen = IsFullLength ? len - FrameExceptDataLength : len;
            }
        }

        /// <summary>
        /// 根据数据创建完整帧数据
        /// </summary>
        /// <param name="tags">标签</param>
        /// <param name="data">数据</param>
        /// <returns>完整帧数据</returns>
        public byte[] CreateFrame(byte[] tags, byte[] data)
        {
            byte[] frame = new byte[FrameExceptDataLength + data.Length];
            Array.Copy(tags, 0, frame, 0, tags.Length);
            switch (ValueLengthType)
            {
                case ValueLengthType.Byte:
                    ByteHelper.TryWriteByte(frame, tags.Length, (byte)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length));
                    break;
                case ValueLengthType.UShort:
                    ByteHelper.TryWriteUShort(frame, tags.Length, (ushort)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), ValueLengthIsLittleEndian);
                    break;
                case ValueLengthType.UInt:
                    ByteHelper.TryWriteUInt(frame, tags.Length, (uint)(IsFullLength ? data.Length + FrameExceptDataLength : data.Length), ValueLengthIsLittleEndian);
                    break;
            }

            Array.Copy(data, 0, frame, tags.Length + ValueLengthType.Length(), data.Length);
            return frame;
        }

        /// <summary>
        /// 解析完整帧数据
        /// </summary>
        /// <param name="frame">完整帧数据</param>
        /// <param name="args">帧解码</param>
        /// <returns>解析是否成功</returns>
        public bool DecodeFrame(byte[] frame, out TagLengthValueFrameDataEventArgs args)
        {
            args = null;

            if (frame.Length >= FrameExceptDataLength)
            {
                byte[] tag = frame.AsSpan().Slice(0, TagLength).ToArray();
                int len = frame.AsSpan().Slice(TagLength, ValueLengthType.Length()).GetLengthValue(ValueLengthIsLittleEndian);
                int frameLen = IsFullLength ? len : len + FrameExceptDataLength;
                if (frameLen == frame.Length)
                {
                    args = new TagLengthValueFrameDataEventArgs(
                        this,
                        tag,
                        len,
                        frame.AsSpan().Slice(TagLength + ValueLengthType.Length()).ToArray(),
                        frame);
                }
            }

            return args != null;
        }
    }
}
